/**
 * www.easyplatform.cn ©2016
 */
package cn.easyplatform.studio.web.editors.support;

import cn.easyplatform.studio.utils.StudioUtil;
import nu.xom.Element;
import nu.xom.Serializer;
import nu.xom.Text;
import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.reflect.FieldUtils;

import java.io.IOException;
import java.io.OutputStream;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.util.StringTokenizer;

import static org.apache.commons.lang3.Validate.notNull;

/**
 * @author <a href="mailto:shiny_vc@163.com">陈云亮</a> <br/>
 * @since 2.0.0 <br/>
 */
public class MultiplexSerializer extends Serializer {

	public MultiplexSerializer(OutputStream out) {
		super(out);
		try {
			Object o = escaper.get(this);

			incrementIndent = StudioUtil.findMethod(o.getClass(),
					"incrementIndent");
			notNull(incrementIndent);
			incrementIndent.setAccessible(true);

			decrementIndent = StudioUtil.findMethod(o.getClass(),
					"decrementIndent");
			notNull(decrementIndent);
			decrementIndent.setAccessible(true);

			setPreserveSpace = StudioUtil.findMethod(o.getClass(),
					"setPreserveSpace", boolean.class);
			notNull(setPreserveSpace);
			setPreserveSpace.setAccessible(true);

		} catch (IllegalAccessException e) {
			throw new RuntimeException(e);
		}

	}

	private static Field escaper;
	private Method incrementIndent;
	private Method decrementIndent;
	private Method setPreserveSpace;

	static {
		try {
			escaper = FieldUtils.getField(Serializer.class, "escaper", true);
		} catch (Exception e) {
			throw new RuntimeException(e);
		}
	}

	@Override
	protected void writeXMLDeclaration() throws IOException {
	}

	@Override
	protected void write(Element element) throws IOException {
		if (ZulXsdUtil.isCodeElement(element)) {

			if (element.getAttribute("src") != null) {
				super.write(element);
				return;
			}

			boolean needsCDATA = !"style".equals(element.getLocalName())
					&& !"attribute".equals(element.getLocalName());

			writeStartTag(element);

			if (needsCDATA) {
				breakLine();
				writeRaw("<![CDATA[");
				incrementIndent();
			}
			preserveWhiteSpace(true);

			for (int i = 0; i < element.getChildCount(); i++) {

				if (!(element.getChild(i) instanceof Text)) {
					writeChild(element.getChild(i));
					continue;
				}

				Text text = (Text) element.getChild(i);
				String source;
				try {
					String tag = element.getLocalName();
					if (tag.equals("script") || tag.equals("zscript")) {
						source = CodeFormatter.formatJS(text.getValue());
					} else if (tag.equals("style")) {
						source = CodeFormatter.formatCSS(text.getValue());
					} else if (tag.equals("html")) {
						source = CodeFormatter.formatHTML(text.getValue());
					} else {
						source = StringUtils.replaceEach(text.getValue(),
								new String[] { "&", "<", ">" }, new String[] {
										"&amp;", "&lt;", "&gt;" });
						String[] c = source.split("\n");
						StringBuilder sb = new StringBuilder();
						if (c[c.length - 1].trim().equals("")) {
							for (int j = 0; j < c.length - 1; j++) {
								sb.append(c[j]);
								if (j < c.length - 2)
									sb.append("\n");
							}
							source = sb.toString();
						}
					}
				} catch (Exception e) {
					throw new RuntimeException(e);
				}
				if (!"attribute".equals(element.getLocalName())) {
					StringTokenizer tokenizer = new StringTokenizer(source,
							"\n", false);
					while (tokenizer.hasMoreTokens()) {
						breakLine();
						writeRaw(tokenizer.nextToken());
					}
				} else
					writeRaw(source);
			}

			preserveWhiteSpace(false);

			if (needsCDATA) {
				decrementIndent();
				breakLine();
				writeRaw("]]>");
			}

			element = (Element) element.copy();
			element.appendChild(new Element("fake"));
			writeEndTag(element);
		} else
			super.write(element);

	}

	private void preserveWhiteSpace(boolean preserve) {
		try {
			setPreserveSpace.invoke(escaper.get(this), preserve);
		} catch (Exception e) {
			throw new RuntimeException(e);
		}
	}

	private void decrementIndent() {
		try {
			decrementIndent.invoke(escaper.get(this));
		} catch (Exception e) {
			throw new RuntimeException(e);
		}
	}

	private void incrementIndent() {
		try {
			incrementIndent.invoke(escaper.get(this));
		} catch (Exception e) {
			throw new RuntimeException(e);
		}
	}
}
